﻿
using System.Runtime.CompilerServices;
using Modules.Units;

namespace ModuleCommon.Protocol
{
    /// <summary>
    ///
    /// Define Callback Action:
    ///    #region [Protocol Parse Callback]
    ///    private Action<Protocol.Frame> OnGetFrameAction = (Protocol.Frame frame) =>
    ///    {
    ///        LogUtil.getInstance(className).log.Info(String.Format("Get a frame: {0} \n", JsonConvert.SerializeObject(frame)));
    ///        if (thisWindow != null)
    ///        {
    ///            thisWindow.Dispatcher.Invoke(DispatcherPriority.Normal, new System.Windows.Forms.MethodInvoker(delegate ()
    ///            {
    ///                MessageBox.Show(String.Format("Get a frame: {0} \n", JsonConvert.SerializeObject(frame)));
    ///            }));
    ///        }
    ///    };
    ///    private Action<string> OnParseErrorAction = (string msg) =>
    ///    {
    ///        LogUtil.getInstance(className).log.Info(String.Format("Get Error when parse buffer, Message is: {0} \n", msg));
    ///    };
    ///    #endregion
    ///
    /// Call Function:
    ///    Analysis.getInstance(OnGetFrameAction, OnParseErrorAction).pushData(buf, null);
    ///
    /// </summary>

    public class Analysis
    {
        private static Analysis mInstance = null;


        /// <summary>
        /// 读到的原始数据
        /// </summary>
        [System.Security.SecuritySafeCritical]
        private List<byte[]> mBufferList = new List<byte[]>();

        /// <summary>
        /// 复制的数据用于线程内部处理
        /// </summary>
        [System.Security.SecuritySafeCritical]
        private List<byte> mFrameList = new List<byte>();

        /// <summary>
        /// 数据解析线程
        /// </summary>
        private Thread parseThread = null;
        private TaskFactory factory = new TaskFactory();

        /// <summary>
        /// 线程信号量
        /// </summary>
        //private Task task = null;
        private Semaphore sema = new Semaphore(0, 1024);

        public static Analysis getInstance()
        {
            return getInstance(null, null);
        }

        public static Analysis getInstance(Action<Frame> OnGetFrame, Action<string, byte[]> OnParseError)
        {
            if (mInstance == null)
            {
                mInstance = new Analysis(OnGetFrame, OnParseError);
            }
            return mInstance;
        }

        public Analysis(Action<Frame> OnGetFrame, Action<string, byte[]> OnParseError)
        {
            setOnGetFrameAction(OnGetFrame);
            setOnParseErrorAction(OnParseError);
        }


        /// <summary>
        /// 读到的串口数据推入解析线程
        /// </summary>
        /// <param name="buffer"></param>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void pushData(byte[] buffer)
        {
            if (buffer == null || buffer.Length == 0) return;

            mBufferList.Add(buffer);

            if (parseThread == null || (parseThread.ThreadState & System.Threading.ThreadState.Stopped) != 0)
            {
                parseThread = new Thread(parseAsyn);
                parseThread.Priority = ThreadPriority.Highest;
                parseThread.Start();
                Console.WriteLine("数据解析线程被创建！\n");
            }

            if (!isAnalysisThreadRunning)
                isAnalysisThreadRunning = true;

            sema.Release(1);
        }

        private bool isAnalysisThreadRunning = false;

        private void stopAnalysisThread()
        {
            if (isAnalysisThreadRunning)
            {
                isAnalysisThreadRunning = false;
                sema.Release(1);
            }
            //
            if (parseThread != null)
            {
                if (parseThread.IsAlive)
                    parseThread.Abort();
                parseThread = null;
            }
        }

        /// <summary>
        /// 处理获取到的串口数据
        /// </summary>
        private void parseAsyn()
        {
            do
            {
                sema.WaitOne();
                if (!isAnalysisThreadRunning) break;

                if (mBufferList.Count == 0 && mFrameList.Count == 0)
                    continue;
                else if (mBufferList.Count != 0 && mBufferList[0] != null)
                {
                    byte[] buffer = mBufferList[0];
                    mFrameList.AddRange(buffer);
                    mBufferList.RemoveAt(0);
                }
                else if (mFrameList.Count == 0)
                {
                    Console.WriteLine("remove null data ...");
                    //mBufferList.RemoveAt(0);
                    continue;
                }

                while (mFrameList.Count != 0)
                {
                    if (mFrameList.Count >= FramePara.HEAD_LEN)
                    {
                        if (findHead(mFrameList))
                        {
                            break;
                        }
                        else //if (mBufferList.Count != 0)
                        {
                            mFrameList.RemoveAt(0);
                        }
                    }
                    else
                    {
                        break;
                    }
                }

                if (mFrameList.Count != 0)
                    try
                    {
                        uint headLen = FramePara.HEAD_LEN + FramePara.ADDR_LEN + FramePara.CMD_LEN + FramePara.LENGTH_LEN + FramePara.PARITY_LEN + FramePara.CHECK_LEN;
                        if (mFrameList.Count < headLen) continue;
                        uint length = (uint)((0x000000FF & ((int)mFrameList[(FramePara.HEAD_LEN + FramePara.CMD_LEN + FramePara.ADDR_LEN)]))
                                + (0x0000FF00 & (((int)mFrameList[(FramePara.HEAD_LEN + FramePara.CMD_LEN + FramePara.ADDR_LEN + 1)]) << 8))
                                + (0x00FF0000 & (((int)mFrameList[(FramePara.HEAD_LEN + FramePara.CMD_LEN + FramePara.ADDR_LEN + 2)]) << 16))
                                + (0xFF000000 & (((int)mFrameList[(FramePara.HEAD_LEN + FramePara.CMD_LEN + FramePara.ADDR_LEN + 3)]) << 24)));
                        uint dataLen = headLen + length;
                        if (mFrameList.Count < dataLen) continue;

                        byte[] buf = mFrameList.GetRange(0, (int)dataLen).ToArray();
                        mFrameList.RemoveRange(0, (int)dataLen);

                        #region[check the frame, after get a whole frame...]
                        checkFrameAsyn(OnGetFrameAction, OnParseErrorAction, buf);
                        // Call Asyn Function by Thread.
                        //new Thread(parseFrameAsyn).Start(new ThreadBean
                        //{
                        //    OnGetFrameAction = OnGetFrameAction,
                        //    OnParseErrorAction = OnParseErrorAction,
                        //    frame = buf
                        //});
                        #endregion

                        if (mBufferList.Count == 0 && mFrameList.Count > 0) //清除二级缓存残留.
                            sema.Release(1);
                    }
                    catch (Exception e)
                    {

                        continue;
                    }
            }
            while (isAnalysisThreadRunning);
        }

        private Action<Frame> OnGetFrameAction { set; get; }
        private Action<string, byte[]> OnParseErrorAction { set; get; }

        public void setOnGetFrameAction(Action<Frame> OnGetFrame)
        {
            this.OnGetFrameAction = OnGetFrame;
        }

        public void setOnParseErrorAction(Action<string, byte[]> OnParseError)
        {
            this.OnParseErrorAction = OnParseError;
        }

        private async void checkFrameAsyn(Action<Frame> OnGetFrame, Action<string, byte[]> OnParseError, byte[] framedata)
        {
            await Task.Run(() =>
            {
                try
                {
                    byte[] buf = framedata;
                    Frame frameIn = new Frame();
                    int ret = frameIn.setBuffer(buf);
                    if (ret != 1 && OnParseError != null)
                    {
                        OnParseError("Something wrong in buffer length! \n", buf);
                    }
                    if (FramePara.HEAD != frameIn.STX) return;



                    //if (!checkSum(buf, frameIn.CRC))
                    //{
                    //    if (OnParseError != null)
                    //    {
                    //        OnParseError(String.Format("Checksum is not passed!  Length = {0}, buffer length = {1}\n", frameIn.LEN, buf.Length), buf);
                    //        //bean.OnParseErrorAction(String.Format("Checksum is not passed, buf is: {0}", HexUtil.byteToHexString(buf)));
                    //    }
                    //    return;
                    //}





                    // AsyncCallback.
                    FrameAsyncCallback(OnGetFrame, frameIn);
                    //Console.WriteLine("Get Whole Frame: {0}", JsonConvert.SerializeObject(frameIn));
                }
                catch (Exception e)
                {

                }
            });
        }

        private void checkFrameAsyn(object obj)
        {
            try
            {
                ThreadPara bean = obj as ThreadPara;
                byte[] buf = bean.framedata;
                Frame frameIn = new Frame();
                int ret = frameIn.setBuffer(buf);
                if (ret != 1 && bean.OnParseErrorAction != null)
                {
                    bean.OnParseErrorAction("Something wrong in buffer length! \n", buf);
                }
                if (FramePara.HEAD != frameIn.STX) return;
                if (!checkSum(buf, frameIn.CRC))
                {
                    if (bean.OnParseErrorAction != null)
                    {
                        bean.OnParseErrorAction(String.Format("Checksum is not passed!  Length = {0}, buffer length = {1}\n", frameIn.LEN, buf.Length), buf);
                        //bean.OnParseErrorAction(String.Format("Checksum is not passed, buf is: {0}", HexUtil.byteToHexString(buf)));
                    }
                    return;
                }
                // AsyncCallback.
                FrameAsyncCallback(bean.OnGetFrameAction, frameIn);
                //
                //Console.WriteLine("Get Whole Frame: {0}", JsonConvert.SerializeObject(frameIn));
            }
            catch (Exception e)
            {

            }
        }

        private async void FrameAsyncCallback(Action<Frame> OnGetFrameAction, Frame frameIn)
        {
            await Task.Run(() =>
            {
                if (OnGetFrameAction != null)
                {
                    try
                    {
                        OnGetFrameAction(frameIn);
                    }
                    catch (Exception e)
                    {

                    }
                }
            });
        }

        public static ushort getChecksumValue(byte[] bytes, bool isHeadInBuffer)
        {
            byte[] arr;
            if (isHeadInBuffer)
            {
                arr = new byte[bytes.Length - FramePara.HEAD_LEN];
                Array.Copy(bytes, FramePara.HEAD_LEN, arr, 0, arr.Length);
            }
            else
            {
                arr = bytes;
            }
            ushort value = 0x0000;
            foreach (byte b in arr)
            {
                //value = (byte) (value ^ b);
                value += (ushort)(0x00FF & b);
            }
            return (ushort)~value;
        }

        public static bool checkSum(byte[] bytes, int src)
        {
            byte[] temp = new byte[bytes.Length - FramePara.CHECK_LEN];
            //Copy the head, cmd and length, parity parts.
            Array.Copy(bytes, 0, temp, 0, FramePara.HEAD_LEN + FramePara.CMD_LEN + FramePara.LENGTH_LEN + FramePara.PARITY_LEN);
            //Copy data part.
            Array.Copy(bytes,
                    FramePara.HEAD_LEN + FramePara.CMD_LEN + FramePara.LENGTH_LEN + FramePara.CHECK_LEN + FramePara.PARITY_LEN,
                    temp, FramePara.HEAD_LEN + FramePara.CMD_LEN + FramePara.LENGTH_LEN + FramePara.PARITY_LEN,
                    temp.Length - FramePara.HEAD_LEN - FramePara.CMD_LEN - FramePara.LENGTH_LEN - FramePara.PARITY_LEN);
            bool isChecked = src == getChecksumValue(temp, true); //(0x0000FFFF & getChecksumValue(temp, true));
            return isChecked;
        }

        public static bool findHead(List<Byte> list)
        {
            if (list.Count < FramePara.HEAD_LEN) return false;
            if ((0x00FF & list[0]) != 0x90) return false;
            if ((0x00FF & list[1]) != 0xEB) return false;
            return true;
        }
    }

    public class ThreadPara
    {
        public Action<Frame> OnGetFrameAction { set; get; }

        public Action<string, byte[]> OnParseErrorAction { set; get; }

        public byte[] framedata { set; get; }
    }
}